#ifndef FNWRAP_H
#define FNWRAP_H

#include "jncInterface.h"
#include "jncDefs.h"
#include <typeinfo>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/function.hpp>
#include <map>
#include <iostream>

// the classes implement some sort of higher order functions
// They are used when keywords __use or __id are requested.
// the former to implicitly pass the instance of an object as
// the first argument to a function and the latter to assign 
// the result of execution to a named variable


gReturnCode getVarValue(std::string);

// TODO: Duplication !!!

#define ARG(r, n, data)  data[n]
/* #define BRANCHFN(z, n, data)						\ */
/*   case n: return fn(BOOST_PP_ENUM(n, ARG, BOOST_PP_SEQ_ELEM(0, data))); */

#define PROXYFN(n)							\
  virtual gReturnCode  CallMember(CallBackParam cn)			\
  {									\
    handle = cn.getHandle();						\
    int i = cn.argv.size();						\
    while(i < BOOST_PP_DEC(JINC_MAX_ARG)) 				\
      {									\
	cn.argv.push_back(jncTypev());					\
	++i;								\
      }									\
    return fn(BOOST_PP_ENUM(BOOST_PP_DEC(JINC_MAX_ARG), ARG, cn.argv)); \
  }
#define TOFN(z, n, data)						\
  case n: 
#define BRANCH(fn)							\
  virtual gReturnCode CallMember(CallBackParam cn)			\
  {									\
    int i = cn.argv.size();						\
    while(i < BOOST_PP_DEC(JINC_MAX_ARG)) 				\
      {									\
	cn.argv.push_back(jncTypev());					\
	++i;								\
      }									\
    return BOOST_PP_CAT(fn, BOOST_PP_SUB(JINC_MAX_ARG, 2))(BOOST_PP_ENUM(BOOST_PP_SUB(JINC_MAX_ARG, 2), ARG, cn.argv)); \
  }


#define  PRINT(z, n, data)					\
  BOOST_PP_CAT(x, BOOST_PP_ADD(n, BOOST_PP_SEQ_ELEM(1, data)))
/* #define ARGS(z, n, data) jncTypev BOOST_PP_CAT(x, n) */
#define FNARG(z, n, data) jncTypev BOOST_PP_CAT(x, BOOST_PP_ADD(n, BOOST_PP_SEQ_ELEM(1, data))) = jncTypev()
#define KWORD(r, n, data)  BOOST_PP_CAT(x, n)
#define FNWRAPPER(z, n, data)						\
  gReturnCode fn(BOOST_PP_ENUM(n, OUTPUTARG, ~)) {			\
    std::vector<jncTypev> t(collectArg(BOOST_PP_ENUM(n, COLARGS, ~))); \
    std::vector<jncTypev> arr((t.begin() + BOOST_PP_SEQ_ELEM(1, data)), (t.begin() + getValidArg(t))); \
    return BOOST_PP_SEQ_ELEM(0, data)(BOOST_PP_ENUM(BOOST_PP_SEQ_ELEM(1, data), KWORD, ~), arr); \
  };

#define OUTPUTARG(z, n, data)			\
  jncTypev BOOST_PP_CAT(x, n) = jncTypev()
#define COLARGS(z, n, data)			\
  BOOST_PP_CAT(x, n)

JncInterface* __use(JncInterface*, jncTypev);
void __id(gReturnCode, jncTypev);


template<class T>
class UseWrapper : public T {
 public :
  UseWrapper(T& x): T(x) {}
    PROXYFN(1)
      BOOST_PP_REPEAT_FROM_TO(BOOST_PP_DEC(JINC_MAX_ARG), JINC_MAX_ARG, FNWRAPPER, (useFn)(1))
      gReturnCode fn() {
      assert(0);
    }
 protected :
    inline gReturnCode useFn(jncTypev what, std::vector<jncTypev> args) {
      JncInterface* base(this);
      base = __use(this, what); // TODO: Assign object instead of ptr
      CallBackParam tmp(handle, args);
      return T::CallMember(tmp);
    }
 private:
    void* handle;
};

template<class T>
class IdWrapper : public T {
 public :
  IdWrapper(T& x): T(x) {}
    PROXYFN(1)
      BOOST_PP_REPEAT_FROM_TO(BOOST_PP_DEC(JINC_MAX_ARG), JINC_MAX_ARG, FNWRAPPER, (idFn)(1))
      gReturnCode fn() {
      assert(0);
    }
 protected:
    inline gReturnCode idFn(jncTypev what, std::vector<jncTypev>& args) {
      CallBackParam tmp(handle, args);
      gReturnCode res =  T::CallMember(tmp);
      __id(res, what);
      return res;
    }
 private:
    void* handle;
};

template<class T>
class UseIdWrapper : public T {
 public :
  UseIdWrapper(T& x): T(x) {}
    PROXYFN(2)
      BOOST_PP_REPEAT_FROM_TO(BOOST_PP_DEC(JINC_MAX_ARG), JINC_MAX_ARG, FNWRAPPER, (useidFn)(2))
      gReturnCode fn() {
      assert(0);
    }
    gReturnCode fn(jncTypev n) {
      assert(0);
    }
 protected :
    gReturnCode useidFn(jncTypev name, jncTypev named, std::vector<jncTypev>& args) {
      JncInterface* p = __use(this, name);
      CallBackParam tmp(handle, args);
      gReturnCode res = T::CallMember(tmp);
      __id(res, named);
      return res;
    }
 private:
    void* handle;
};

#endif

